package org.aplikator.server.descriptor;

import org.aplikator.client.shared.data.ListItem;
import org.aplikator.client.shared.data.Record;
import org.aplikator.client.shared.descriptor.PropertyDTO;
import org.aplikator.server.Context;
import org.aplikator.server.data.BinaryData;
import org.aplikator.server.query.QueryCompareExpression;
import org.aplikator.server.query.QueryCompareOperator;
import org.aplikator.server.query.QueryParameterReference;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

public class Property<T extends Serializable> extends ServerDescriptorBase implements Cloneable {

    private final Class<T> type;

    private final Entity entity;

    private Reference<? extends Entity> refferedThrough;

    private ListProvider listProvider;

    private final double size;

    private final boolean required;

    private boolean editable;

    public Property(String id, Class<T> type, double size, boolean required, Entity entity) {
        super(id);
        this.type = type;
        this.entity = entity;
        this.size = size;
        this.required = required;
        this.setEditable(true);
    }

    public Class<T> getType() {
        return type;
    }

    public PropertyDTO clientClone(Context ctx) {
        PropertyDTO clientRefThrough = null;
        if (refferedThrough != null) {
            clientRefThrough = refferedThrough.clientClone(ctx);
        }
        PropertyDTO retval = new PropertyDTO(getId(), getLocalizedName(ctx), getType().getName(), getSize(), clientRefThrough, editable);
        if (listProvider != null) {
            List<ListItem> clientListValues = listProvider.getListValues(ctx);
            retval.setListValues(clientListValues);
        }
        return retval;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Property<T> clone() throws CloneNotSupportedException {
        return (Property<T>) super.clone();
    }

    public Entity getEntity() {
        return entity;
    }

    public ListProvider getListProvider() {
        return listProvider;
    }

    public Property<T> setListProvider(ListProvider listProvider) {
        this.listProvider = listProvider;
        return this;
    }

    public double getSize() {
        return size;
    }

    public boolean isRequired() {
        return required;
    }

    public Property<T> setEditable(boolean editable) {
        this.editable = editable;
        return this;
    }

    public boolean isEditable(){
        return editable;
    }

    /**
     * Creates the default widget for this property, based on its type.
     *
     * @return The default widget or null (for unsupported types, currently
     *         Reference and Collection) TODO - add support for references and
     *         collections, avoid infinite recursion
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public Widget widget() {
        Widget retval = null;
        if (listProvider != null) {
            retval = new ComboBox(this);
        } else if (type.equals(Boolean.class)) {
            retval = new CheckBox((Property<Boolean>) this);
        } else if (type.equals(Date.class)) {
            retval = new DateField((Property<Date>) this);
        } else if (type.equals(BinaryData.class)) {
            retval = new BinaryField((Property<BinaryData>) this);
        } else if (type.equals(Date.class)) {
            retval = new DateField((Property<Date>) this);
        } else if (this instanceof Reference) {
            retval = null;//ReferenceField.reference((Reference) this);// TODO avoid
                                                                // circular
                                                                // references
        } else if (this instanceof Collection) {
            retval = null;
        } else if (type.equals(String.class) && size == 0) {
            retval = new TextArea((Property<String>) this);
        } else {
            retval = new TextField(this);
        }
        return retval;

    }

    public Reference<? extends Entity> getRefferedThrough() {
        return refferedThrough;
    }

    void setRefferedThrough(Reference<? extends Entity> refferedThrough) {
        this.refferedThrough = refferedThrough;
    }

    String getRecordId(){
        if (getRefferedThrough()!= null){
            return getRefferedThrough().getRecordId()+PATH_DELIMITER+getId();
        } else {
            return getId();
        }
    }

    @SuppressWarnings("unchecked")
    public T getValue(Record record){
        return (T) record.getValue(getRecordId());
    }

    public void setValue(Record record, T value){
        record.setValue(getRecordId(),value);
    }

    /*Query support methods*/

    public QueryCompareExpression<T> EQUAL(T value){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.EQUAL,value);
    }

    public QueryCompareExpression<T> EQUAL(QueryParameterReference ref){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.EQUAL,ref);
    }

    public QueryCompareExpression<T> NOTEQUAL(T value){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.NOTEQUAL,value);
    }

    public QueryCompareExpression<T> NOTEQUAL(QueryParameterReference ref){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.NOTEQUAL,ref);
    }



    public QueryCompareExpression<T> LESSTHAN(T value){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.LESSTHAN,value);
    }

    public QueryCompareExpression<T> LESSTHAN(QueryParameterReference ref){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.LESSTHAN,ref);
    }

    public QueryCompareExpression<T> MOREOREQUAL(T value){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.MOREOREQUAL,value);
    }

    public QueryCompareExpression<T> MOREOREQUAL(QueryParameterReference ref){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.MOREOREQUAL,ref);
    }

    public QueryCompareExpression<T> GREATERTHAN(T value){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.GREATERTHAN,value);
    }

    public QueryCompareExpression<T> GREATERTHAN(QueryParameterReference ref){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.GREATERTHAN,ref);
    }

    public QueryCompareExpression<T> LESSOREQUAL(T value){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.LESSOREQUAL,value);
    }

    public QueryCompareExpression<T> LESSOREQUAL(QueryParameterReference ref){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.LESSOREQUAL,ref);
    }

    public QueryCompareExpression<T> LIKE(T value){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.LIKE,value);
    }

    public QueryCompareExpression<T> LIKE(QueryParameterReference ref){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.LIKE,ref);
    }

    public QueryCompareExpression<T> NOTLIKE(T value){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.NOTLIKE,value);
    }

    public QueryCompareExpression<T> NOTLIKE(QueryParameterReference ref){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.NOTLIKE,ref);
    }

    @SuppressWarnings("unchecked")
    public QueryCompareExpression<T> NULL(){
        return new QueryCompareExpression(this, QueryCompareOperator.NULL, (T)null);
    }

    @SuppressWarnings("unchecked")
    public QueryCompareExpression<T> NOTNULL(){
        return new QueryCompareExpression<T>(this, QueryCompareOperator.NOTNULL,(T) null);
    }


    public QueryCompareExpression<T> BETWEEN(T min, T max){
        throw new UnsupportedOperationException("BETWEEN is not yet implemented");
        //return new QueryCompareExpression<T>(this, QueryCompareOperator.BETWEEN,min, max);
    }

    public QueryCompareExpression<T> BETWEEN(QueryParameterReference refMin, QueryParameterReference refMax){
        throw new UnsupportedOperationException("BETWEEN is not yet implemented");
        //return new QueryCompareExpression<T>(this, QueryCompareOperator.BETWEEN,refMin, refMax);
    }

    public QueryCompareExpression<T> NOTBETWEEN(T min, T max){
        throw new UnsupportedOperationException("NOTBETWEEN is not yet implemented");
        //return new QueryCompareExpression<T>(this, QueryCompareOperator.NOTBETWEEN,min, max);
    }

    public QueryCompareExpression<T> NOTBETWEEN(QueryParameterReference refMin, QueryParameterReference refMax){
        throw new UnsupportedOperationException("NOTBETWEEN is not yet implemented");
        //return new QueryCompareExpression<T>(this, QueryCompareOperator.NOTBETWEEN,refMin,refMax );
    }

    public QueryCompareExpression<T> IN(T[] value){
        throw new UnsupportedOperationException("IN is not yet implemented");
        //return new QueryCompareExpression<T>(this, QueryCompareOperator.IN,value);
    }

    public QueryCompareExpression<T> IN(QueryParameterReference[] ref){
        throw new UnsupportedOperationException("IN is not yet implemented");
        //return new QueryCompareExpression<T>(this, QueryCompareOperator.IN,ref);
    }

    public QueryCompareExpression<T> NOTIN(T[] value){
        throw new UnsupportedOperationException("NOTIN is not yet implemented");
        //return new QueryCompareExpression<T>(this, QueryCompareOperator.NOTIN,value);
    }

    public QueryCompareExpression<T> NOTIN(QueryParameterReference[] ref){
        throw new UnsupportedOperationException("NOTIN is not yet implemented");
        //return new QueryCompareExpression<T>(this, QueryCompareOperator.NOTIN,ref);
    }
}
